﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Collections;

namespace ParserGenerator.Extensions
{
    public static class IDictionaryExtensions
    {
        public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            return new ReadOnlyDictionary<TKey, TValue>(map);
        }

        public static ReadOnlyDictionary<TKey, ReadOnlyCollection<TValueItem>> AsReadOnly<TKey, TValueItem>(this IDictionary<TKey, List<TValueItem>> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            return map.AsReadOnly(true);
        }

        public static ReadOnlyDictionary<TKey, ReadOnlyCollection<TValueItem>> AsReadOnly<TKey, TValueItem>(this IDictionary<TKey, List<TValueItem>> map, bool toArray)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            var dict = new Dictionary<TKey, ReadOnlyCollection<TValueItem>>(map.Count);
            foreach (var t in map)
                if (toArray)
                    dict.Add(t.Key, t.Value.ToArray().AsReadOnly());
                else
                    dict.Add(t.Key, t.Value.AsReadOnly());
            return dict.AsReadOnly();
        }

        public static ReadOnlyDictionary<TKey, ReadOnlyLinkedList<TValueItem>> AsReadOnly<TKey, TValueItem>(this IDictionary<TKey, LinkedList<TValueItem>> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            var dict = new Dictionary<TKey, ReadOnlyLinkedList<TValueItem>>(map.Count);
            foreach (var t in map)
                dict.Add(t.Key, t.Value.AsReadOnly());
            return dict.AsReadOnly();
        }

        public static ReadOnlyDictionary<TKey, ReadOnlySet<TValueItem>> AsReadOnly<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            var dict = new Dictionary<TKey, ReadOnlySet<TValueItem>>(map.Count);
            foreach (var t in map)
                dict.Add(t.Key, t.Value.AsReadOnly());
            return dict.AsReadOnly();
        }

        public static ReadOnlyDictionary<TKey, ReadOnlySet<TValueItem>> AsReadOnly<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            var dict = new Dictionary<TKey, ReadOnlySet<TValueItem>>(map.Count);
            foreach (var t in map)
                dict.Add(t.Key, t.Value.AsReadOnly());
            return dict.AsReadOnly();
        }

        public static TValue TryGetValueOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> map, TKey key) where TValue : new()
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            TValue value;
            if (!map.TryGetValue(key, out value))
            {
                value = new TValue();
                map.Add(key, value);
            }
            return value;
        }

        public static HashSet<TValueItem> TryGetValueOrCreate<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map, TKey key, IEqualityComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            HashSet<TValueItem> value;
            if (!map.TryGetValue(key, out value))
            {
                value = new HashSet<TValueItem>(comparer);
                map.Add(key, value);
            }
            return value;
        }

        public static SortedSet<TValueItem> TryGetValueOrCreate<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map, TKey key, IComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            SortedSet<TValueItem> value;
            if (!map.TryGetValue(key, out value))
            {
                value = new SortedSet<TValueItem>(comparer);
                map.Add(key, value);
            }
            return value;
        }

        public static void AddValueItem<TKey, TValueItem>(this IDictionary<TKey, List<TValueItem>> map, TKey key, TValueItem item)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            List<TValueItem> list = map.TryGetValueOrCreate(key);
            list.Add(item);
        }

        public static void AddValueItem<TKey, TValueItem>(this IDictionary<TKey, LinkedList<TValueItem>> map, TKey key, TValueItem item)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            LinkedList<TValueItem> list = map.TryGetValueOrCreate(key);
            list.AddLast(item);
        }

        public static bool AddValueItem<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map, TKey key, TValueItem item)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            HashSet<TValueItem> set = map.TryGetValueOrCreate(key);
            return set.Add(item);
        }

        public static bool AddValueItem<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map, TKey key, TValueItem item, IEqualityComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            HashSet<TValueItem> set = map.TryGetValueOrCreate(key, comparer);
            return set.Add(item);
        }

        public static bool AddValueItem<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map, TKey key, TValueItem item)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            SortedSet<TValueItem> set = map.TryGetValueOrCreate(key);
            return set.Add(item);
        }

        public static bool AddValueItem<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map, TKey key, TValueItem item, IComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            SortedSet<TValueItem> set = map.TryGetValueOrCreate(key, comparer);
            return set.Add(item);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, List<TValueItem>> map, TKey key, IEnumerable<TValueItem> items)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            List<TValueItem> list = map.TryGetValueOrCreate(key);
            list.AddRange(items);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, LinkedList<TValueItem>> map, TKey key, IEnumerable<TValueItem> items)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            LinkedList<TValueItem> list = map.TryGetValueOrCreate(key);
            foreach (TValueItem item in items)
                list.AddLast(item);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map, TKey key, IEnumerable<TValueItem> items)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            HashSet<TValueItem> set = map.TryGetValueOrCreate(key);
            set.UnionWith(items);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, HashSet<TValueItem>> map, TKey key, IEnumerable<TValueItem> items, IEqualityComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            HashSet<TValueItem> set = map.TryGetValueOrCreate(key, comparer);
            set.UnionWith(items);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map, TKey key, IEnumerable<TValueItem> items)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            SortedSet<TValueItem> set = map.TryGetValueOrCreate(key);
            set.UnionWith(items);
        }

        public static void AddValueItems<TKey, TValueItem>(this IDictionary<TKey, SortedSet<TValueItem>> map, TKey key, IEnumerable<TValueItem> items, IComparer<TValueItem> comparer)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            if (items == null)
                throw new ArgumentNullException(nameof(items));
            SortedSet<TValueItem> set = map.TryGetValueOrCreate(key, comparer);
            set.UnionWith(items);
        }

        public static bool DictionaryEquals<TKey, TValue>(this IDictionary<TKey, TValue> map, IDictionary<TKey, TValue> other)
        {
            if (map == other)
                return true;
            if (map == null)
                return false;
            if (other == null)
                return false;
            if (map.Count != other.Count)
                return false;
            IEqualityComparer<TValue> comparer = EqualityComparer<TValue>.Default;
            foreach (var t in map)
            {
                TValue value;
                if (!other.TryGetValue(t.Key, out value))
                    return false;
                if (!comparer.Equals(t.Value, value))
                    return false;
            }
            return true;
        }

        public static int GetDictionaryHashCode<TKey, TValue>(this IDictionary<TKey, TValue> map)
        {
            if (map == null)
                throw new ArgumentNullException(nameof(map));
            int h = 0;
            foreach (var t in map)
            {
                int x = ICollectionExtensions.GetItemHashCode(t.Key);
                int y = ICollectionExtensions.GetItemHashCode(t.Value);
                int c = ICollectionExtensions.CombineHashCodes(x, y);
                h ^= c & 0x7fffffff;
            }
            return h;
        }
    }
}
